﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;
using System.Text;
using System.Linq;

namespace ZephyrisNet_WP7
{
    public class NetworkMainClass
    {
        private int packetID = 1;

        private bool isP2PJoined = false;

        private bool isLinkConnected = false;

        public bool IsLinkConnected
        {
            get { return isLinkConnected; }
        }
        private bool isSuperNode = false;

        public bool IsSuperNode
        {
            get { return isSuperNode; }
            set { isSuperNode = value; }
        }

        private Constants.NetworkMode networkMode;
        private IPAddress serverIPAddress = null;
        private int serverPort = -1;

        private Dictionary<IPEndPoint, DateTime> lastConnections = new Dictionary<System.Net.IPEndPoint, DateTime>();

        private NetworkLinkClass networkLink;
        private UdpAnySourceMulticastClient multicastSocket;

        private bool isBufferLocked = false;

        // Buffer for incoming data
        private byte[] receiveBuffer = new byte[Constants.receiveBufferLength];

        // Buffer for incoming P2P data
        private byte[] receiveP2PBuffer = new byte[Constants.receiveBufferLength];

        // Datapacket for incoming P2P data
        private DataPacket receiveDataPacket = new DataPacket();

        public bool StartNetwork()
        {
            IPAddress localhostIp = IPAddress.Parse("127.0.0.1");
            return StartNetwork(Constants.NetworkMode.LAN, localhostIp, -1);
        }

        public bool StartNetwork(Constants.NetworkMode networkMode, IPAddress serverIPAddress, int serverPort)
        {
            if (networkMode == Constants.NetworkMode.LAN_Online) this.isSuperNode = true;
            else this.isSuperNode = false;

            this.networkMode = networkMode;
            this.serverIPAddress = serverIPAddress;
            this.serverPort = serverPort;

            try
            {
                //if (this.networkMode == Constants.NetworkMode.LAN_Online || this.networkMode == Constants.NetworkMode.WAN)
                //{
                //    // if we need a dedicated server, set one up here
                //    mainServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                //    var connectionOperation = new SocketAsyncEventArgs { RemoteEndPoint = new DnsEndPoint(this.serverIPAddress.ToString(), this.serverPort) };
                //}

                //if (this.networkMode == Constants.NetworkMode.LAN_Online || this.networkMode == Constants.NetworkMode.LAN)
                //{
                //    // if LAN based use multicast too
                //    SetupMultiCast();
                //}

                SetupMultiCast();

                if (this.networkMode == Constants.NetworkMode.LAN_Online)
                {
                    // LAN Online mode selected, we will need a socket to a standard TCP Server, maybe for authorisation
                    // and authentication

                    SetupLink();
                }
 
            }
            catch
            {
                return false;
            }

            return true;
        }

        private void SetupLink()
        {
            networkLink = new NetworkLinkClass();
            networkLink.CreateConnection(this.serverIPAddress.ToString(), this.serverPort);

            isLinkConnected = true;
        }

        private void SetupMultiCast()
        {
            multicastSocket = new UdpAnySourceMulticastClient(Constants.multicastIP, Constants.multicastPort);

            multicastSocket.BeginJoinGroup(
                result =>
                {
                    multicastSocket.EndJoinGroup(result);
                    multicastSocket.MulticastLoopback = false;
                    isP2PJoined = true;

                }, null);
        }

        public bool SendP2P(string data)
        {
            byte[] buffer = Encoding.UTF8.GetBytes(data);
            return SendP2P(buffer);
        }

        public bool SendP2P(byte[] buffer)
        {
            if (isP2PJoined)
            {
                try
                {
                    // Build header and append to data before sending
                    string headerString = packetID.ToString("D6") + ";";

                    byte[] filler = new byte[6] { 1, 1, 1, 1, 1, 1 };
                    byte[] header = Encoding.UTF8.GetBytes(headerString);
                    byte[] outputBuffer = new byte[header.Length + buffer.Length];

                    Array.Copy(filler, outputBuffer, 0);
                    Array.Copy(header, 0, outputBuffer, 0, header.Length);
                    Array.Copy(buffer, 0, outputBuffer, header.Length, buffer.Length);

                    if (packetID == 999999) packetID = 1;
                    else packetID++;

                    multicastSocket.BeginSendToGroup(outputBuffer, 0, outputBuffer.Length,
                        result =>
                        {
                            multicastSocket.EndSendToGroup(result);
                        }, null);
                    return true;
                }
                catch
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }

        public void Send(string data)
        {
            // convert text to send (prefix with length)
            var message = string.Format("{0};{1}", data.Length, data);
            var buffer = Encoding.UTF8.GetBytes(message);

            Send(buffer);
        }

        private void Send(byte[] buffer)
        {
            if (isLinkConnected && isSuperNode)
            {
                networkLink.SendData(buffer);
            }

        }

        public void ReceiveP2P()
        {
            Thread THRD_Receive = new Thread(ReceiveP2PLoop);
            THRD_Receive.Start();
        }

        private void ReceiveP2PLoop()
        {
            if (isP2PJoined)
            {
                Array.Clear(receiveP2PBuffer, 0, receiveP2PBuffer.Length);
                multicastSocket.BeginReceiveFromGroup(receiveP2PBuffer, 0, receiveP2PBuffer.Length,
                    result =>
                    {
                        IPEndPoint source;

                        // Complete the asynchronous operation. The source field will 
                        // contain the IP address of the device that sent the message
                        multicastSocket.EndReceiveFromGroup(result, out source);

                        // Get the received data from the buffer.
                        string header_dataReceived = Encoding.UTF8.GetString(receiveP2PBuffer, 0, receiveP2PBuffer.Length);
                        string[] dataReceived = header_dataReceived.Split(';');

                        // Fill and return a data packet
                        receiveDataPacket.packetID = int.Parse(dataReceived[0]);
                        receiveDataPacket.Data = dataReceived[1];
                        receiveDataPacket.Endpoint = source;
                        receiveDataPacket.Timestamp = DateTime.Now;

                        ReceiveP2PLoop();

                    }, null);
                
            }
            else
            {
                throw new Exception("P2P not joined exception");
            }
        }

        public void Receive()
        {
            Thread THRD_Receive = new Thread(ReceiveLoop);
            THRD_Receive.Start();
        }

        private void ReceiveLoop()
        {
            networkLink.ReceiveData();

            while (!networkLink.BufferIsFull)
            {
                isBufferLocked = true;
                receiveBuffer.Concat(networkLink.ReceiveBuffer);
            }

            isBufferLocked = false;
        }

        public DataPacket GetDataPacket()
        {
            DataPacket dataPacket = new DataPacket();

            dataPacket = receiveDataPacket;
            receiveDataPacket = new DataPacket();

            return dataPacket;
        }

        public byte[] GetBuffer()
        {
            if (!isBufferLocked) return receiveBuffer;
            else return new byte[0];
        }

        public List<IPEndPoint> GetConnections()
        {
            return new List<System.Net.IPEndPoint>(lastConnections.Keys);
        }

        public bool P2PJoined()
        {
            return this.isP2PJoined;
        }

        public bool ServerJoined()
        {
            return this.isLinkConnected;
        }
    }
}
